/************************************************************
* @file   gkt_clkrst.c
***********************************************************/

#include "gkt_board.h"
#include "gkt_debug.h"

#define CLKRST_DESC_COMMON_TYPE(common_desc)	\
	((common_desc).type & GKT_CLKRST_DESC_TYPE_MASK)


#if defined(GKT_CLKRST_REGION_NUMS) && (GKT_CLKRST_REGION_NUMS > 1)
static const uint32_t sc_clkrst_baseaddr_table[] __SYSINFO_RODATA = {
	GKT_CLKRST_REGION_BASEADDRS
};
#define clkrst_baseaddr(desc_ptr)	\
	sc_clkrst_baseaddr_table[((gkt_clkrst_base_desc_s *)(desc_ptr))->region_id]
#else
#define clkrst_baseaddr(desc_ptr)	GKT_CLKRST_BASEADDR
#endif

/* clock table */
#undef GKT_CLKRST_CLOCK_ITEM
#define GKT_CLKRST_CLOCK_ITEM(name)	GKT_CLKRST_##name##_CLOCK
static uint32_t s_clkrst_clock_table[] __SYSINFO_DATA = {
	GKT_CLKRST_CLOCK_ITEMS
};
#define clkrst_clock(clkid)	s_clkrst_clock_table[clkid]

void gkt_clkrst_enable_clock(const gkt_clkrst_desc_u *desc)
{
	uint32_t reg_addr, reg_value;

	reg_addr = clkrst_baseaddr(desc) + desc->base.offset;
	reg_value = gkt_read_reg32(reg_addr);
	reg_value |= 1 << desc->base.clken_shift;
	gkt_write_reg32(reg_addr, reg_value);
}

void gkt_clkrst_disable_clock(const gkt_clkrst_desc_u *desc)
{
	uint32_t reg_addr, reg_value;

	reg_addr = clkrst_baseaddr(desc) + desc->base.offset;
	reg_value = gkt_read_reg32(reg_addr);
	reg_value &= ~(1 << desc->base.clken_shift);
	gkt_write_reg32(reg_addr, reg_value); 
}

int gkt_clkrst_clock_is_enabled(const gkt_clkrst_desc_u *desc)
{
	uint32_t reg_addr, reg_value;

	reg_addr = clkrst_baseaddr(desc) + desc->base.offset;
	reg_value = gkt_read_reg32(reg_addr);
	return (reg_value & (1 << desc->base.clken_shift)) ? 1 : 0;
}

static int clkrst_config_fdiv_clock(const gkt_clkrst_fdiv_desc_s *fdiv_desc)
{
	uint32_t src_clock;

	/* 1st. enable clock */
	gkt_clkrst_enable_clock((const gkt_clkrst_desc_u *)fdiv_desc);

	/* 2nd. update clock table */
	if (fdiv_desc->common.clkid < GKT_CLKRST_CLOCK_ID_MAX) {
		src_clock = clkrst_clock(fdiv_desc->common.src_clkid);
		clkrst_clock(fdiv_desc->common.clkid) = src_clock / fdiv_desc->divider;
	}

	return GKT_SUCCESS;
}

static uint32_t clkrst_cdiv_get_clkdiv(const gkt_clkrst_cdiv_desc_s *cdiv_desc, 
					uint32_t clock, uint32_t src_clock)
{
	uint32_t clkdiv;

	if (!clock)
		clkdiv = (src_clock + cdiv_desc->attr.clock_max - 1) / cdiv_desc->attr.clock_max;
	else if (clock <= cdiv_desc->attr.clock_max)
		clkdiv = (src_clock + clock - 1) / clock;
	else
		return 0;

	if (GKT_CLKRST_CLKDIV_MASKS == cdiv_desc->attr.clkdiv_type) {
		do {
			if (cdiv_desc->attr.clkdiv.masks & gkt_bit(clkdiv))
				return clkdiv;
			clkdiv++;
		} while (clkdiv < GKT_INTBITS);
	}
	else if (GKT_CLKRST_CLKDIV_EVEN == cdiv_desc->attr.clkdiv_type) {
		clkdiv = (clkdiv + 1) & (~1U);
		if (clkdiv <= cdiv_desc->attr.clkdiv.range.max) {
			if (clkdiv < cdiv_desc->attr.clkdiv.range.min)
				clkdiv = cdiv_desc->attr.clkdiv.range.min;
			return clkdiv;
		}
	}

	return 0;
}

static int clkrst_config_cdiv_clock(const gkt_clkrst_cdiv_desc_s *cdiv_desc, 
					uint32_t clock)
{
	uint32_t src_clock, clkdiv;
	uint32_t reg_addr, reg_value;

	src_clock = clkrst_clock(cdiv_desc->common.src_clkid);
	clkdiv = clkrst_cdiv_get_clkdiv(cdiv_desc, clock, src_clock);
	if (clkdiv) {
		/* 1st. read origin value */
		reg_addr = clkrst_baseaddr(cdiv_desc) + cdiv_desc->common.offset;
		reg_value = gkt_read_reg32(reg_addr);

		/* 2nd. disable clock */
		reg_value &= ~ gkt_bit(cdiv_desc->common.clken_shift);
		gkt_write_reg32(reg_addr, reg_value);

		/* 3rd. set new divider */
		reg_value &= ~ gkt_mask(cdiv_desc->attr.clkdiv_bits, cdiv_desc->attr.clkdiv_shift);
		reg_value |= clkdiv << cdiv_desc->attr.clkdiv_shift;
		gkt_write_reg32(reg_addr, reg_value);

		/* 4th. enable clock */
		reg_value |= gkt_bit(cdiv_desc->common.clken_shift);
		gkt_write_reg32(reg_addr, reg_value);

		/* 5th. update clock table */
		if (cdiv_desc->common.clkid < GKT_CLKRST_CLOCK_ID_MAX)
			clkrst_clock(cdiv_desc->common.clkid) = src_clock / clkdiv;

		return GKT_SUCCESS;
	}

	return GKT_EPARAM;
}

int gkt_clkrst_config_clock(const gkt_clkrst_desc_u *desc)
{
	uint32_t desc_type;
	int retval;

	desc_type = CLKRST_DESC_COMMON_TYPE(desc->base);
	if (GKT_CLKRST_DESC_FDIV == desc_type)
		retval = clkrst_config_fdiv_clock((const gkt_clkrst_fdiv_desc_s *)desc);
	else if (GKT_CLKRST_DESC_CDIV == desc_type)
		retval = clkrst_config_cdiv_clock((const gkt_clkrst_cdiv_desc_s *)desc, 0);
	else if (GKT_CLKRST_DESC_BASE == desc_type) {
		gkt_clkrst_enable_clock(desc);
		retval = GKT_SUCCESS;
	}
	else
		retval = GKT_ENOTSUPPORT;

	return retval;
}

void gkt_clkrst_select_dbclk(const gkt_clkrst_desc_u *desc, 
			uint32_t dbclk_type)
{
	uint32_t reg_addr, reg_value, mask;

	if (desc->base.type & GKT_CLKRST_DESC_ET_DBCLK) {
		mask = gkt_mask(desc->base_dbclk.dbclk_bits, desc->base_dbclk.dbclk_shift);

		reg_addr = clkrst_baseaddr(desc) + desc->base.offset;
		reg_value = gkt_read_reg32(reg_addr);
		reg_value &= ~mask;
		reg_value |= (dbclk_type << desc->base_dbclk.dbclk_shift)  & mask;
		gkt_write_reg32(reg_addr, reg_value);
	}
}

static int clkrst_config_msclk_clock(const gkt_clkrst_msclk_desc_s *msclk_desc, 
				const gkt_clkrst_msclk_attr_s *attr)
{
	uint32_t src_clock, clkdiv[GKT_CLKRST_MSCLK_MAX];
	uint32_t reg_addr, reg_value;
	uint32_t i;

	src_clock = clkrst_clock(msclk_desc->common.src_clkid);
	for (i = GKT_CLKRST_MSCLK_MCLK; i < GKT_CLKRST_MSCLK_MAX; i++) {
		if (!attr->clock[i] 
			|| attr->clock[i] >= msclk_desc->attrs[i].clock_max)
		{
			return GKT_ENOTSUPPORT;
		}

		clkdiv[i] = src_clock / attr->clock[i];
		if (GKT_CLKRST_CLKDIV_MASKS == msclk_desc->attrs[i].clkdiv_type) {
			do {
				if (msclk_desc->attrs[i].clkdiv.masks & gkt_bit(clkdiv[i]))
					goto config_clock;
				clkdiv[i]++;
			} while (clkdiv[i] < GKT_INTBITS);
	
			return GKT_ENOTSUPPORT;
		}
		else if (GKT_CLKRST_CLKDIV_EVEN == msclk_desc->attrs[i].clkdiv_type) {
			clkdiv[i] = (clkdiv[i] + 1) & (~1U);
			if (clkdiv[i] < msclk_desc->attrs[i].clkdiv.range.min)
				clkdiv[i] = msclk_desc->attrs[i].clkdiv.range.min;
			else if (clkdiv[i] > msclk_desc->attrs[i].clkdiv.range.max)
				return GKT_ENOTSUPPORT;
		}
		else
			return GKT_ENOTSUPPORT;

		src_clock = attr->clock[i];
	}

config_clock:
	/* 1st. read origin value */
	reg_addr = clkrst_baseaddr(msclk_desc) + msclk_desc->common.offset;
	reg_value = gkt_read_reg32(reg_addr);

	/* 2nd. disable clock */
	reg_value &= ~ gkt_bit(msclk_desc->common.clken_shift);
	gkt_write_reg32(reg_addr, reg_value);

	/* 3rd. set new divider */
	reg_value &= ~ (gkt_mask(msclk_desc->attrs[GKT_CLKRST_MSCLK_MCLK].clkdiv_bits, 
								msclk_desc->attrs[GKT_CLKRST_MSCLK_MCLK].clkdiv_shift)
					| gkt_mask(msclk_desc->attrs[GKT_CLKRST_MSCLK_SCLK].clkdiv_bits, 
								msclk_desc->attrs[GKT_CLKRST_MSCLK_SCLK].clkdiv_shift));
	reg_value |= clkdiv[GKT_CLKRST_MSCLK_MCLK] << msclk_desc->attrs[GKT_CLKRST_MSCLK_MCLK].clkdiv_shift;
	reg_value |= clkdiv[GKT_CLKRST_MSCLK_SCLK] << msclk_desc->attrs[GKT_CLKRST_MSCLK_SCLK].clkdiv_shift;
	gkt_write_reg32(reg_addr, reg_value);

	/* 4th. enable clock */
	reg_value |= gkt_bit(msclk_desc->common.clken_shift);
	gkt_write_reg32(reg_addr, reg_value);

	return GKT_SUCCESS;
}

int gkt_clkrst_config_clock_ext(const gkt_clkrst_desc_u *desc, 
				uint32_t clock)
{
	uint32_t desc_type;
	int retval;

	GKT_ASSERT(desc);

	desc_type = CLKRST_DESC_COMMON_TYPE(desc->base);
	if (GKT_CLKRST_DESC_CDIV == desc_type)
		retval = clkrst_config_cdiv_clock((const gkt_clkrst_cdiv_desc_s *)desc, clock);
	else if (GKT_CLKRST_DESC_MSCLK == desc_type) {
		retval = clkrst_config_msclk_clock((const gkt_clkrst_msclk_desc_s *)desc,
					(const gkt_clkrst_msclk_attr_s *)clock);
	}
	else {
//		GKT_ASSERT(0);
		retval = GKT_ENOTSUPPORT;
	}

	return retval;
}

uint32_t gkt_clkrst_get_src_clock(const gkt_clkrst_desc_u *desc)
{
	return clkrst_clock(desc->base.src_clkid);
}

uint32_t gkt_clkrst_get_clock(const gkt_clkrst_desc_u *desc)
{
	uint32_t reg_addr, reg_value;
	uint32_t src_clock, clkdiv, desc_type;
	uint32_t clock = 0;

	src_clock = clkrst_clock(desc->base.src_clkid);

	desc_type = CLKRST_DESC_COMMON_TYPE(desc->base);
	if (GKT_CLKRST_DESC_FDIV == desc_type) {
		GKT_ASSERT(desc->fdiv.divider);
		clock = src_clock / (uint32_t)desc->fdiv.divider;
	}
	else if (GKT_CLKRST_DESC_CDIV == desc_type) {
		reg_addr = clkrst_baseaddr(desc) + desc->base.offset;
		reg_value = gkt_read_reg32(reg_addr);
		if (reg_value & (1U << desc->base.clken_shift)) {
			/* clock is enabled */
			clkdiv = reg_value & gkt_mask(desc->cdiv.attr.clkdiv_bits, desc->cdiv.attr.clkdiv_shift);
			clkdiv >>= desc->cdiv.attr.clkdiv_shift;
			GKT_ASSERT(clkdiv);
			clock = src_clock / clkdiv;
		}
	}

	return clock;
}

uint32_t gkt_clkrst_get_next_clock(const gkt_clkrst_desc_u *desc,
				uint32_t *clock_div)
{
	uint32_t src_clock, clkdiv, desc_type;
	uint32_t clock = 0;

	if (clock_div) {
		clkdiv = *clock_div;
		src_clock = clkrst_clock(desc->base.src_clkid);

		desc_type = CLKRST_DESC_COMMON_TYPE(desc->base);
		if (GKT_CLKRST_DESC_CDIV == desc_type) {
			if (GKT_CLKRST_CLKDIV_MASKS == desc->cdiv.attr.clkdiv_type) {
				while (clkdiv < GKT_CLKRST_CLKDIV_MASKS_MAX_DIV) {
					clkdiv++;
					if (gkt_bit(clkdiv) & desc->cdiv.attr.clkdiv.masks) {
						clock = src_clock / clkdiv;
						if (clock <= desc->cdiv.attr.clock_max) {
							*clock_div = clkdiv;
							break;
						}

						clock = 0;
					}
				}
			}
			else if (GKT_CLKRST_CLKDIV_EVEN == desc->cdiv.attr.clkdiv_type) {
				while (clkdiv <= desc->cdiv.attr.clkdiv.range.max) {
					if (clkdiv >= desc->cdiv.attr.clkdiv.range.min)
						clkdiv += 2;
					else
						clkdiv = desc->cdiv.attr.clkdiv.range.min;

					clock = src_clock / clkdiv;
					if (clock <= desc->cdiv.attr.clock_max) {
						*clock_div = clkdiv;
						break;
					}

					clock = 0;						
				}
			}
		}
		else if (GKT_CLKRST_DESC_FDIV == desc_type) {
			if (!clkdiv) {
				clock = src_clock / desc->fdiv.divider;
				*clock_div = (uint32_t)desc->fdiv.divider;
			}
		}
	}

	return clock;
}

void gkt_clkrst_reset_peripheral(const gkt_clkrst_desc_u *desc, uint32_t time_us)
{
	uint32_t reg_addr, reg_value;

	if (desc->base.rstn_shift < GKT_CLKRST_SHIFT_MAX) {
		/* disable clock and reset */
		reg_addr = clkrst_baseaddr(desc) + desc->base.offset;
		reg_value = gkt_read_reg32(reg_addr);
		reg_value &= ~(1 << desc->base.rstn_shift);
		gkt_write_reg32(reg_addr, reg_value);

		gkt_delay_us(time_us);

		reg_value |= 1 << desc->base.rstn_shift;
		gkt_write_reg32(reg_addr, reg_value);
	}
}

